refactor(ios): Migrate from PrivateSentrySDKOnly to SentrySDK.internal#6380
refactor(ios): Migrate from PrivateSentrySDKOnly to SentrySDK.internal#6380alwx wants to merge 17 commits into
Conversation
Semver Impact of This PR⚪ None (no version bump detected) 📋 Changelog PreviewThis is how your changes will appear in the changelog.
🤖 This preview updates automatically when you update the PR. |
|
@cursor review |
Replace the deprecated PrivateSentrySDKOnly SPI and SentrySwizzle.h macro with the new SentrySDK.internal Swift API exposed by sentry-cocoa 9.19.0, via a thin in-pod ObjC<->Swift bridge (RNSentryInternal.swift). Imports Sentry with @_spi(Private) for SPI-gated sub-APIs (performance, replay, envelope). Drops the performSelector workaround around getReplayIntegration since SentrySDK.internal.replay.capture() returns Bool directly. Closes #6370 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Move RNSentryStartTests, RNSentryStartFromFileTests, and RNSentryReplayOptionsTests
to SentrySDK.internal.{options, options(fromDictionary:), appStart.hybridSDKMode,
performance.framesTrackingHybridSDKMode}.
The ObjC RNSentryTests.m keeps PrivateSentrySDKOnly for now — the test target
is a separate consumer of the RNSentry static-lib pod and cannot see
RNSentry-Swift.h without modulemap surgery. Worth revisiting when sentry-cocoa
removes PrivateSentrySDKOnly in the next major.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`SentryInternalSwizzleApi` in sentry-cocoa has no platform gating, and `SENTRY_HAS_UIKIT` (which guards the `[RNSentryRNSScreen swizzleViewDidAppear]` call in RNSentry.mm) covers visionOS. The Swift bridge was gated to `iOS/tvOS/macCatalyst` only, silently no-oping the swizzle on visionOS where the deprecated `SentrySwizzle` macro used to work. Also documents why `setCurrentScreen` remains a visionOS no-op: sentry-cocoa's `SentryInternalScreenApi` is intentionally gated to iOS/tvOS, so the new API surface itself doesn't expose the setter on visionOS. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
b850f46 to
330236b
Compare
📲 Install BuildsAndroid
|
iOS (legacy) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 7a89652+dirty | 3861.46 ms | 1229.61 ms | -2631.85 ms |
| 5b7e8a7+dirty | 3851.22 ms | 1223.79 ms | -2627.43 ms |
| 5569641+dirty | 3839.22 ms | 1231.30 ms | -2607.91 ms |
| 9210ae6+dirty | 3815.93 ms | 1214.14 ms | -2601.79 ms |
| 88735e9+dirty | 3837.02 ms | 1214.40 ms | -2622.62 ms |
| 09a902f+dirty | 3835.67 ms | 1217.11 ms | -2618.57 ms |
| 5a23c47+dirty | 3855.46 ms | 1221.95 ms | -2633.50 ms |
| 94af3bd+dirty | 3849.30 ms | 1235.15 ms | -2614.15 ms |
| 5789645+dirty | 3841.18 ms | 1220.67 ms | -2620.52 ms |
| 1e5d96d+dirty | 3851.45 ms | 1212.05 ms | -2639.41 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 7a89652+dirty | 5.15 MiB | 6.70 MiB | 1.55 MiB |
| 5b7e8a7+dirty | 5.15 MiB | 6.70 MiB | 1.54 MiB |
| 5569641+dirty | 5.15 MiB | 6.67 MiB | 1.51 MiB |
| 9210ae6+dirty | 5.15 MiB | 6.68 MiB | 1.53 MiB |
| 88735e9+dirty | 4.98 MiB | 6.46 MiB | 1.49 MiB |
| 09a902f+dirty | 4.98 MiB | 6.46 MiB | 1.49 MiB |
| 5a23c47+dirty | 4.98 MiB | 6.46 MiB | 1.49 MiB |
| 94af3bd+dirty | 5.15 MiB | 6.69 MiB | 1.53 MiB |
| 5789645+dirty | 4.98 MiB | 6.50 MiB | 1.52 MiB |
| 1e5d96d+dirty | 4.98 MiB | 6.46 MiB | 1.49 MiB |
iOS (new) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 7a89652+dirty | 3827.11 ms | 1211.49 ms | -2615.62 ms |
| 5b7e8a7+dirty | 3855.46 ms | 1234.47 ms | -2620.99 ms |
| 5569641+dirty | 3824.35 ms | 1210.78 ms | -2613.57 ms |
| 9210ae6+dirty | 3834.11 ms | 1216.64 ms | -2617.47 ms |
| 88735e9+dirty | 3837.80 ms | 1216.39 ms | -2621.41 ms |
| 09a902f+dirty | 3847.65 ms | 1221.31 ms | -2626.34 ms |
| 5a23c47+dirty | 3843.54 ms | 1226.05 ms | -2617.50 ms |
| 94af3bd+dirty | 3847.84 ms | 1230.33 ms | -2617.51 ms |
| 40c9884+dirty | 3826.11 ms | 1217.04 ms | -2609.07 ms |
| 1e5d96d+dirty | 3845.93 ms | 1222.51 ms | -2623.42 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 7a89652+dirty | 5.15 MiB | 6.70 MiB | 1.55 MiB |
| 5b7e8a7+dirty | 5.15 MiB | 6.70 MiB | 1.54 MiB |
| 5569641+dirty | 5.15 MiB | 6.67 MiB | 1.51 MiB |
| 9210ae6+dirty | 5.15 MiB | 6.68 MiB | 1.53 MiB |
| 88735e9+dirty | 4.98 MiB | 6.46 MiB | 1.49 MiB |
| 09a902f+dirty | 4.98 MiB | 6.46 MiB | 1.49 MiB |
| 5a23c47+dirty | 4.98 MiB | 6.46 MiB | 1.49 MiB |
| 94af3bd+dirty | 5.15 MiB | 6.69 MiB | 1.53 MiB |
| 40c9884+dirty | 4.98 MiB | 6.51 MiB | 1.53 MiB |
| 1e5d96d+dirty | 4.98 MiB | 6.46 MiB | 1.49 MiB |
antonis
left a comment
There was a problem hiding this comment.
Overall looks good. I think a conditional import like should fix the build issues 🤞
#if __has_include(<RNSentry/RNSentry-Swift.h>)
#import <RNSentry/RNSentry-Swift.h>
#else
#import "RNSentry-Swift.h"
#endif
`withUnsafePointer(to:)` only guarantees the pointer stays valid inside the closure body. Capturing it in `keyPtr` and using it outside the closure was undefined behaviour by contract (even though `static var` storage happens to be stable in practice). Wrap the entire `instanceMethod` call inside `withUnsafePointer` so the pointer is always used within its documented lifetime; the `static var` backing storage keeps the address itself stable across calls, so sentry-cocoa's `oncePerClass` dedup keeps working. Flagged by Cursor Bugbot. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Handle both static-lib and framework-style Pod integrations by falling back from `<RNSentry/RNSentry-Swift.h>` (frameworks path) to the local `"RNSentry-Swift.h"` (static-lib path) via `__has_include`. Applied to every `.m`/`.mm` in packages/core/ios that consumes the Swift bridge. Suggested by @antonis. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two fixes flagged by Warden: 1. `RNSentryInternal.envelope(fromData:)` accepted a non-optional Swift `Data`. If the ObjC caller passed a nil `NSData*` (e.g. from a failed base64 decode of the envelope payload), the ObjC→Swift bridge would force-unwrap it and crash before the method body ran. Change the parameter to `Data?` with an internal `guard let` — matches the nil-tolerant behaviour of the deprecated `PrivateSentrySDKOnly.envelopeWithData:`. 2. Swift Cocoa tests accessed `SentrySDK.internal.*` with plain `import Sentry`. The `.internal` accessor is public today, but the sub-APIs the tests touch (`options`, `appStart`, `performance`) sit next to `@_spi(Private)`-gated siblings on the same struct. Add `@_spi(Private) import Sentry` to `RNSentryStartTests.swift`, `RNSentryStartFromFileTests.swift`, and `RNSentryReplayOptionsTests.swift` so they keep compiling if the surface is ever tightened. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
When `captureViewHierarchy` returned nil the ObjC code still constructed an empty `NSMutableArray` and resolved with it. In JS, `[]` is truthy — the caller would treat the failure as a successful (empty) attachment. Bail out with `resolve(nil)` up front so the JS side sees the actual failure signal. Pre-existing since 2023; flagged by Seer while reviewing this PR. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Cover the bridge's own contract — nil-guard on `envelope(fromData:)`, metadata accessors, options factory, hybrid-SDK-mode flags, and the `swizzleRNSScreenViewDidAppear` early-return when RNSScreen is unavailable. sentry-cocoa owns the underlying `SentrySDK.internal.*` behaviour; we just assert the wrapper forwards correctly and honours the documented nil / platform guards. Registers the new file in the RNSentryCocoaTester project explicitly (the target does not use file-system-synchronized groups yet). Flagged by Warden. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adding Swift to the RNSentry pod means CocoaPods refuses to integrate it against non-modular ObjC dependencies. On older React Native versions (e.g. 0.71 with Hermes) the React-hermes pod does not define modules, so `pod install` fails with: The Swift pod `RNSentry` depends upon `React-hermes`, which does not define modules. Inject `use_modular_headers!` into the Podfile via the existing e2e patch script, and document the same knob for downstream users in the CHANGELOG. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 2cb3360. Configure here.
Cherry-picked from #6410. Dependabot bumped `codeql-action/analyze` and `codeql-action/autobuild` to v4.36.3 but left `init` on v4.36.2, so `init` writes a config stamped 4.36.2 that `analyze` (4.36.3) rejects: Loaded a configuration file for version '4.36.2', but running version '4.36.3' Pull this fix into the PR branch so CI stops failing while #6410 is still open on main. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Previous version unconditionally set `modularPatched = true` inside the `use_modular_headers!` branch, so a Podfile without a `prepare_react_native_project!` anchor would be reported as successfully patched even though `content.replace` returned it unchanged. Compare replace output against the input and log a warning instead when the anchor is missing. Flagged by Cursor Bugbot. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The migration to `SentrySDK.internal.screen.setCurrent` drops the current-screen breadcrumb enrichment on visionOS because sentry-cocoa's new `SentryInternalScreenApi` is gated to iOS/tvOS only. Already documented in a code comment; call it out for downstream users too. Flagged by Seer. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Android (legacy) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| d2eadf8+dirty | 414.64 ms | 454.56 ms | 39.92 ms |
| 4966363+dirty | 400.04 ms | 431.08 ms | 31.04 ms |
| 0307fa4+dirty | 454.16 ms | 518.35 ms | 64.18 ms |
| 038a6d7+dirty | 524.82 ms | 531.92 ms | 7.10 ms |
| 4e0b819+dirty | 420.56 ms | 470.08 ms | 49.52 ms |
| 1a5721e+dirty | 424.07 ms | 482.32 ms | 58.25 ms |
| a0d8cf8+dirty | 411.71 ms | 467.57 ms | 55.87 ms |
| 9474ead+dirty | 411.45 ms | 446.80 ms | 35.35 ms |
| ad66da3+dirty | 468.46 ms | 533.56 ms | 65.10 ms |
| 0b1b5e3+dirty | 416.42 ms | 470.58 ms | 54.16 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| d2eadf8+dirty | 48.30 MiB | 53.48 MiB | 5.18 MiB |
| 4966363+dirty | 48.30 MiB | 53.54 MiB | 5.24 MiB |
| 0307fa4+dirty | 49.74 MiB | 54.81 MiB | 5.07 MiB |
| 038a6d7+dirty | 48.30 MiB | 53.60 MiB | 5.30 MiB |
| 4e0b819+dirty | 49.74 MiB | 54.81 MiB | 5.07 MiB |
| 1a5721e+dirty | 49.74 MiB | 55.09 MiB | 5.34 MiB |
| a0d8cf8+dirty | 48.30 MiB | 53.49 MiB | 5.19 MiB |
| 9474ead+dirty | 48.30 MiB | 53.61 MiB | 5.30 MiB |
| ad66da3+dirty | 48.30 MiB | 53.49 MiB | 5.19 MiB |
| 0b1b5e3+dirty | 48.30 MiB | 53.60 MiB | 5.29 MiB |
`RNSentryInternal` is a Swift type compiled into the `RNSentry` pod module, not the `Sentry` module. Without `@_spi(Private) import RNSentry` the test target cannot resolve `RNSentryInternal.*` and fails to build. Flagged by Warden. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

📢 Type of change
📜 Description
Replace deprecated
PrivateSentrySDKOnlySPI andSentrySwizzle.hmacro usage in our iOS code with the newSentrySDK.internalSwift API exposed by sentry-cocoa 9.19.0, via a thin in-pod ObjC↔Swift bridge (RNSentryInternal.swift).The bridge uses
@_spi(Private) import Sentryto reach SPI-gated sub-APIs (performance.currentScreenFrames,replay.configure,envelope.{store,capture,deserialize}). Existing.m/.mmcallers route through[RNSentryInternal …]via the auto-generatedRNSentry-Swift.h. TheperformSelector:@selector(getReplayIntegration)workaround incaptureReplayWithReturnValueis gone —SentrySDK.internal.replay.capture()returnsBooldirectly.Notably, we do not use sentry-cocoa's new
SentryObjCumbrella module (which the issue suggested) — that module isn't shipped via theSentryCocoaPod in 9.19.0, only via SPM as a separate xcframework. The Swift bridge works equally on both consumption paths.💡 Motivation and Context
Closes #6370.
PrivateSentrySDKOnlyis deprecated in sentry-cocoa 9.19.0 and slated for removal in the next major. Migrating now avoids future churn and drops the private header search path /performSelector:reliance.💚 How did you test it?
yarn build,yarn test,yarn lint,yarn circularDepCheck— all greenxcodebuildfor theRNSentrypod target (iphonesimulator) — builds cleanxcodebuild testforRNSentryCocoaTester— 158/158 native Cocoa tests pass📝 Checklist
sendDefaultPIIis enabled🔮 Next steps